#ifndef _GWBASE_BASE_LOGGING_H_
#define _GWBASE_BASE_LOGGING_H_

#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <sstream>
#include <string>
#include <boost/noncopyable.hpp>
#include "gwbase/base/string_util.h"

// Logging framework design
//  Generally, a logging framework need implement two parts:
//    * an easy way for user to assemble a single log message
//    * a location to store log messages
//
// LogEntry
//  represents a single log message, prepend header and append trailer.
//  When LogEntry is destructed, it will write the whole log message
//  into current LogSink.
//
// LogSink
//  It is where LogEntry to write log message. By default, it uses
//  console as output destination. You can use "SetRollingFileLogging()"
//  to change this behavior.

namespace dicom {

class LogSink : public boost::noncopyable {
 public:
  LogSink() {}
  virtual ~LogSink() {}

  virtual void WriteLog(const void* buf, size_t len) = 0;
  virtual void Flush() = 0;
};

extern LogSink* GetCurrentSink();
extern LogSink* CreateRollingFileLogSink(const std::string& base_dir,
                                         const std::string& progname);

class SourceLocation {
 public:
  SourceLocation(const char* file, int line) : line_(line) {
    file_ = strrchr(file, '/');
    assert(file_ != NULL);
    ++file_;
  }

  const char* file() const { return file_; }
  int line() const { return line_; }

 private:
  const char* file_;
  int line_;
};

#define HERE dicom::SourceLocation(__FILE__, __LINE__)

enum LogLevel {
  kLogTrace = 0,
  kLogDebug,
  kLogInfo,
  kLogWarn,
  kLogError,
  kLogFatal
};

extern void SetLogLevel(LogLevel level);
extern LogLevel GetLogLevel();
extern void SetRollingFileLogging(const std::string& base_dir,
                                  const std::string& progname);

class LogEntry {
 public:
  // create a entry, then append messages by << operator
  LogEntry(LogLevel level, const SourceLocation& loc);

  // Write this entry to log file when destroyed
  ~LogEntry();

  template <class T>
  LogEntry& operator<<(const T& t) {
    os_ << t;
    return *this;
  }

  // void LogV(const char* fmt, ...);

 private:
  void WriteHeader();
  void WriteTrailer();

  LogLevel level_;
  std::ostringstream os_;
  SourceLocation loc_;
};

extern std::string PosixErrorMsg(int err_num);
inline std::string PosixErrorMsg() { return PosixErrorMsg(errno); }

// LOG_DEBUG << "msg1" << data1;
#define LOG_TRACE                               \
  if (dicom::GetLogLevel() <= dicom::kLogTrace) \
  dicom::LogEntry(dicom::kLogTrace, HERE)

#define LOG_DEBUG                               \
  if (dicom::GetLogLevel() <= dicom::kLogDebug) \
  dicom::LogEntry(dicom::kLogDebug, HERE)

#define LOG_INFO                               \
  if (dicom::GetLogLevel() <= dicom::kLogInfo) \
  dicom::LogEntry(dicom::kLogInfo, HERE)

#define LOG_WARN                               \
  if (dicom::GetLogLevel() <= dicom::kLogWarn) \
  dicom::LogEntry(dicom::kLogWarn, HERE)

#define LOG_ERROR                               \
  if (dicom::GetLogLevel() <= dicom::kLogError) \
  dicom::LogEntry(dicom::kLogError, HERE)

#define LOG_FATAL                               \
  if (dicom::GetLogLevel() <= dicom::kLogFatal) \
  dicom::LogEntry(dicom::kLogFatal, HERE)

// LOG_DEBUG_V("%s: %d", "abc", 123);
/*
#define LOG_DEBUG_V(fmt, ...) \
    if (dicom::GetLogLevel() <= dicom::kLogDebug)  { \
      dicom::LogEntry(dicom::kLogDebug,              \
                            __FILE__, __LINE__).LogV(fmt, __VARGS__) \
    }
*/
}

#endif  // _GWBASE_BASE_LOGGING_H_
